package crmdna.member;

import static crmdna.common.OfyService.ofy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import crmdna.client.Client;
import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.Utils;
import crmdna.contactdetail.ContactDetailProp;
import crmdna.group.Group;
import crmdna.member.MemberCore.MemberProp;
import crmdna.program.Program;
import crmdna.program.ProgramProp;
import crmdna.programtype.ProgramType;
import crmdna.sequence.Sequence;
import crmdna.sequence.Sequence.SequenceType;
import crmdna.user.User;
import crmdna.user.UserCore;

public class Member {

	public static MemberProp create(String client, long groupId,
			ContactDetailProp contact, String login) {

		Client.ensureValidClient(client);
		UserCore.ensureValidUser(client, login);

		// ensure valid center
		Group.safeGet(client, groupId);

		// do not allow duplicate emails
		boolean allowDuplicateEmail = false;
		MemberProp memberProp = MemberCore.create(client, groupId, contact,
				allowDuplicateEmail);
		return memberProp;
	}

	public static MemberProp createAllowDuplicateEmail(String client,
			long groupId, ContactDetailProp contact, String login) {

		Client.ensureValidClient(client);
		UserCore.ensureValidUser(client, login);

		// ensure valid center
		Group.safeGet(client, groupId);

		// allow duplicate emails
		boolean allowDuplicateEmail = true;
		MemberProp memberProp = MemberCore.create(client, groupId, contact,
				allowDuplicateEmail);
		return memberProp;
	}

	public static List<Long> bulkCreateWithoutValidations(String client,
			List<ContactDetailProp> contactDetailProps, long programId) {

		ProgramProp programProp = Program.safeGet(client, programId).toProp();

		// some elements in contactDetailProps can be null
		int numContacts = 0;
		for (ContactDetailProp c : contactDetailProps) {
			if (c != null)
				numContacts++;
		}

		List<Long> memberIds = new ArrayList<>();
		if (numContacts == 0)
			return memberIds;

		List<Long> ids = Sequence.getNext(client, SequenceType.MEMBER,
				numContacts);

		List<MemberEntity> toAdd = new ArrayList<>();

		long programTypeId = programProp.programTypeProp.programTypeId;
		List<Long> practiceIds = ProgramType.getPracticeIds(client,
				programTypeId);

		int i = 0;
		for (int index = 0; index < contactDetailProps.size(); index++) {
			ContactDetailProp c = contactDetailProps.get(index);
			if (c == null) {
				memberIds.add(null);
				continue;
			}

			long id = ids.get(i);

			MemberEntity memberEntity = new MemberEntity();
			memberEntity.memberId = id;
			MemberCore.populateContactDetails(memberEntity, c);
			memberEntity.groupIds.add(c.centerId);
			memberEntity.programIds.add(programId);
			MemberCore.populateDependantFieldsWithoutDBQuery(memberEntity);

			// other index fields
			memberEntity.programTypeIds.add(programTypeId);
			memberEntity.practiceIds.addAll(practiceIds);

			toAdd.add(memberEntity);

			memberIds.add(id);
			i++;
		}

		ofy(client).save().entities(toAdd);
		return memberIds;
	}

	public static void addOrDeleteProgram(String client, long programId,
			List<Long> memberIds, boolean add, String login) {

		Client.ensureValidClient(client);
		User.ensureValidUser(client, login);

		Program.safeGet(client, programId);

		Map<Long, MemberEntity> map = ofy(client).load()
				.type(MemberEntity.class).ids(memberIds);

		List<MemberEntity> toSave = new ArrayList<>();

		for (Long memberId : map.keySet()) {
			MemberEntity entity = map.get(memberId);

			if (entity != null) {
				boolean change;
				if (add)
					change = entity.programIds.add(programId);
				else
					change = entity.programIds.remove(programId);

				if (change) {
					toSave.add(entity);
				}
			}
		}

		MemberCore.populateDependantFields(client, toSave);

		ofy(client).save().entities(toSave);
	}

	static void bulkUpdateWithoutValidations_notused(String client,
			List<MemberEntity> memberEntities,
			List<ContactDetailProp> contacts, List<Long> groupIds,
			long programId) {

		Client.ensureValidClient(client);

		ProgramProp programProp = Program.safeGet(client, programId).toProp();

		if (contacts.size() != groupIds.size())
			throw new APIException(Status.ERROR_RESOURCE_INCORRECTLY_SPECIFIED,
					"No of contacts do not match number of groups in bulkUpdateWithoutValidations");

		if (contacts.size() != memberEntities.size())
			throw new APIException(
					Status.ERROR_RESOURCE_INCORRECTLY_SPECIFIED,
					"No of contacts do not match number of memberentities in bulkUpdateWithoutValidations");

		// no validations will be done

		long programTypeId = programProp.programTypeProp.programTypeId;
		List<Long> practiceIds = ProgramType.getPracticeIds(client,
				programTypeId);

		for (int i = 0; i < contacts.size(); i++) {

			MemberEntity memberEntity = memberEntities.get(i);
			Long groupId = null;
			if (i < groupIds.size()) {
				groupId = groupIds.get(i);
			}
			if (groupId == null)
				groupId = programProp.groupProp.groupId;

			MemberCore.populateContactDetails(memberEntities.get(i),
					contacts.get(i));
			memberEntity.groupIds.add(groupId);
			memberEntity.programIds.add(programId);

			// dependent fields
			MemberCore.populateDependantFieldsWithoutDBQuery(memberEntity);
			memberEntity.programTypeIds.add(programTypeId);
			memberEntity.practiceIds.addAll(practiceIds);
		}

		// async save
		ofy(client).save().entities(memberEntities);
	}

	public static MemberEntity safeGet(String client, long memberId,
			String login) {
		Client.ensureValidClient(client);
		UserCore.ensureValidUser(client, login);

		return MemberCore.safeGet(client, memberId);
	}

	static MemberProp updateContactDetails(String client, long memberId,
			ContactDetailProp contact, String login) {

		Client.ensureValidClient(client);
		UserCore.ensureValidUser(client, login);

		MemberProp memberProp = MemberCore.updateContactDetails(client,
				memberId, contact);
		return memberProp;
	}

	public static MemberProp addOrDeleteGroup(String client, long memberId,
			long groupId, boolean add, String login) {

		Client.ensureValidClient(client);
		UserCore.ensureValidUser(client, login);

		Group.safeGet(client, groupId);

		MemberProp memberProp = MemberCore.addOrDeleteGroup(client, memberId,
				groupId, add);
		return memberProp;
	}

	public static MemberProp addOrDeleteProgram(String client, long memberId,
			long programId, boolean add, String login) {

		// should be wrapped in a transaction
		Client.ensureValidClient(client);
		UserCore.ensureValidUser(client, login);

		MemberEntity memberEntity = Member.safeGet(client, memberId, login);
		Program.safeGet(client, programId);

		boolean change;
		if (add)
			change = memberEntity.programIds.add(programId);
		else
			change = memberEntity.programIds.remove(programId);

		if (change) {
			MemberCore.populateDependantFields(client, memberEntity);
			ofy(client).save().entity(memberEntity);
		}

		return memberEntity.toProp();
	}

	// public static void bulkUploadProgramParticipants(String client,
	// List<ContactDetailProp> contactDetails,
	// List<Long> groupIds, long programId, String login) {
	//
	// Client.ensureValidClient(client);
	//
	// ProgramProp programProp = Program.safeGet(client, programId).toProp();
	//
	// //user should have permission
	// User.ensureAccess(client, login, ResourceType.PROGRAM,
	// programProp.groupProp.groupId, Action.WRITE);
	//
	// if (contactDetails.size() != groupIds.size())
	// throw new APIException(Status.ERROR_RESOURCE_INCORRECTLY_SPECIFIED,
	// "Number of groupIds [" + groupIds.size() +
	// "] does not match number of contacts ["
	// + contactDetails.size() + "]");
	//
	// for (int i = 0; i < groupIds.size(); i++) {
	// if (groupIds.get(i) == null)
	// groupIds.set(i, programProp.groupProp.groupId);
	// }
	//
	// //ensure group ids are valid if not null
	// Group.ensureValidGroupIds(client, groupIds);
	//
	// //ensure all contacts are valid.
	// ContactDetail.ensureValid(client, contactDetails);
	//
	// //find out the list of emails that exist
	// List<ContactDetailProp> validEmailContactDetails = new ArrayList<>();
	// List<ContactDetailProp> invalidEmailcontactDetails = new ArrayList<>();
	//
	// Set<String> allEmails = new HashSet<>();
	// Set<String> allPhoneNumbers = new HashSet<>();
	// Map<String, ContactDetailProp> allEmailToContactDetails = new
	// HashMap<>();
	// Map<String, Long> allEmailToGroupId = new HashMap<>();
	// for (int i = 0; i < contactDetails.size(); i++) {
	// ContactDetailProp c = contactDetails.get(i);
	//
	// if (Utils.isValidEmailAddress(contactDetails.get(i).email)) {
	// c.email = c.email.toLowerCase();
	// if (allEmails.contains(c.email))
	// throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS,
	// "Duplicate email address [" + c.email + "] in record " + (i + 1) + "]");
	//
	// validEmailContactDetails.add(c);
	// allEmails.add(c.email);
	// allEmailToContactDetails.put(c.email, c);
	// allEmailToGroupId.put(c.email, groupIds.get(i));
	//
	// } else {
	// invalidEmailcontactDetails.add(c);
	// if (c.mobilePhone != null)
	// allPhoneNumbers.add(c.mobilePhone);
	// if (c.mobilePhone != null)
	// allPhoneNumbers.add(c.homePhone);
	// if (c.officePhone != null)
	// allPhoneNumbers.add(c.officePhone);
	// }
	// }
	//
	// //for contactdetails with email check how many are already present in db
	// Query<MemberEntity> emailQuery =
	// ofy(client).load().type(MemberEntity.class)
	// .filter("email in", allEmails);
	// List<MemberEntity> validEmailExisting = emailQuery.list();
	//
	// Query<MemberEntity> phoneQuery =
	// ofy(client).load().type(MemberEntity.class)
	// .filter("phoneNumbers in", allPhoneNumbers);
	// List<MemberEntity> invalidEmailExisting = phoneQuery.list();
	//
	// //valid email - existing
	// Map<String, ContactDetailProp> newEmailToContactDetails = new
	// HashMap<>();
	// newEmailToContactDetails.putAll(allEmailToContactDetails);
	// for (MemberEntity memberEntity : validEmailExisting) {
	// //update the contact details
	// MemberCore.populateContactDetails(memberEntity,
	// allEmailToContactDetails.get(memberEntity.email));
	// memberEntity.programIds.add(programId);
	// MemberCore.populateDependantIndexFields(client, memberEntity);
	// Long groupId = allEmailToGroupId.get(memberEntity.email);
	// if (null != groupId)
	// memberEntity.groupIds.add(allEmailToGroupId.get(memberEntity.email));
	// newEmailToContactDetails.remove(memberEntity.email);
	// }
	//
	// //save asynchronously
	// ofy(client).save().entities(validEmailExisting);
	//
	// //valid email - new
	// List<MemberEntity> validEmailNew = new ArrayList<>();
	// List<Long> memberIds = Sequence.getNext(client, SequenceType.MEMBER,
	// newEmailToContactDetails.size());
	// int i = 0;
	// for (String email : newEmailToContactDetails.keySet()) {
	// ContactDetailProp c = newEmailToContactDetails.get(email);
	// MemberEntity memberEntity = new MemberEntity();
	// memberEntity.memberId = memberIds.get(i);
	// MemberCore.populateContactDetails(memberEntity, c);
	// Long groupId = allEmailToGroupId.get(email);
	// if (null == groupId)
	// groupId = programProp.groupProp.groupId;
	// memberEntity.groupIds.add(groupId);
	// memberEntity.programIds.add(programId);
	// MemberCore.populateDependantIndexFields(client, memberEntity);
	//
	// validEmailNew.add(memberEntity);
	// }
	//
	// //save asynchronously
	// ofy(client).save().entities(validEmailNew);
	//
	//
	// throw new APIException(Status.ERROR_NOT_IMPLEMENTED, "work in progress");
	// }

	// private static long getIndexByPhoneNumber(List<ContactDetailProp>
	// contacts, String phoneNumber) {
	// for (int i = 0; i < contacts.size(); i++) {
	// if (phoneNumber.equals(contacts.get(i).mobilePhone))
	// return i;
	// if (phoneNumber.equals(contacts.get(i).officePhone))
	// return i;
	// if (phoneNumber.equals(contacts.get(i).homePhone))
	// return i;
	// }
	//
	// return -1;
	// }

	public static List<MemberProp> query(String client, String searchString,
			Set<Long> groupIds, Set<Long> practiceIds, Integer maxResultSize,
			String login) {
		Client.ensureValidClient(client);
		UserCore.ensureValidUser(client, login);

		List<MemberProp> memberProps = MemberCore.query(client, searchString,
				groupIds, practiceIds, maxResultSize);
		return memberProps;
	}

	public static Long getMatchingMemberId(String client, String email,
			String firstName, String lastName) {
		List<ContactDetailProp> contactDetailProps = new ArrayList<>();

		ContactDetailProp c = new ContactDetailProp();
		c.firstName = firstName;
		c.lastName = lastName;
		c.email = email;

		contactDetailProps.add(c);

		return getMatchingMemberIds(client, contactDetailProps).get(0);
	}

	public static List<Long> getMatchingMemberIds(String client,
			List<ContactDetailProp> contactDetailProps) {

		Client.ensureValidClient(client);

		if (contactDetailProps.size() == 0)
			return new ArrayList<>();

		for (ContactDetailProp contactDetailProp : contactDetailProps) {
			if (contactDetailProp.email != null)
				contactDetailProp.email = contactDetailProp.email.toLowerCase();
		}

		Map<String, Set<Integer>> emailMap = new HashMap<>();
		Map<String, Set<Integer>> mobileMap = new HashMap<>();

		for (int index = 0; index < contactDetailProps.size(); index++) {
			ContactDetailProp c = contactDetailProps.get(index);
			if (c.email != null) {
				if (!emailMap.containsKey(c.email))
					emailMap.put(c.email, new HashSet<Integer>());

				Set<Integer> existing = emailMap.get(c.email);
				existing.add(index);
			} else if (c.mobilePhone != null) {
				if (!mobileMap.containsKey(c.mobilePhone))
					mobileMap.put(c.mobilePhone, new HashSet<Integer>());

				Set<Integer> existing = mobileMap.get(c.mobilePhone);
				existing.add(index);
			}
		}

		List<Long> memberIds = new ArrayList<>();
		for (int i = 0; i < contactDetailProps.size(); i++) {
			memberIds.add(null);
		}

		List<MemberEntity> emailMatches = new ArrayList<>();
		if (emailMap.size() != 0)
			emailMatches = ofy(client).load().type(MemberEntity.class)
					.filter("email in", emailMap.keySet()).list();

		// System.out.println("emailMap = " + new Gson().toJson(emailMap));
		// System.out.println("emailMatches = " + new
		// Gson().toJson(emailMatches));

		List<MemberEntity> mobileMatches = new ArrayList<>();
		if (mobileMap.size() != 0) {
			mobileMatches = ofy(client).load().type(MemberEntity.class)
					.filter("mobilePhone in", mobileMap.keySet()).list();
		}

		// System.out.println("mobileMap = " + new Gson().toJson(mobileMap));
		// System.out.println("mobileMatches = " + new
		// Gson().toJson(mobileMatches));

		for (MemberEntity me : emailMatches) {
			MemberProp mp = me.toProp();
			String memberName = mp.contact.getName();

			for (Integer index : emailMap.get(mp.contact.email)) {
				String particantName = contactDetailProps.get(index).getName();

				// System.out.println("memberName = [" + memberName +
				// ", participant name = [" +
				// particantName + "]");
				if (Utils.closeEnough(memberName, particantName))
					memberIds.set(index, mp.memberId);
			}
		}

		for (MemberEntity me : mobileMatches) {
			MemberProp mp = me.toProp();
			String memberName = mp.contact.getName();

			for (Integer index : mobileMap.get(mp.contact.mobilePhone)) {
				if (memberIds.get(index) == null) {
					String particantName = contactDetailProps.get(index)
							.getName();

					if (Utils.closeEnough(memberName, particantName))
						memberIds.set(index, mp.memberId);
				}
			}
		}

		return memberIds;
	}

	public static List<MemberProp> getAllForGroup(String client, long groupId,
			String login) {
		Client.ensureValidClient(client);
		Group.safeGet(client, groupId);
		UserCore.ensureValidUser(client, login);

		return MemberCore.getAllForGroup(client, groupId);
	}
}